package concordia.comp6231.rfmulticast;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

import javax.net.ssl.HostnameVerifier;

import concordia.comp6231.common.ApplicationConfig;

class RequestHandler extends Thread {

	private int port;
	private Message msg;
	private ResultHandler sucessHandler;
	private ResultHandler failureHandler;
	private List<String> recipients = new ArrayList<String>();

	private byte buffer[] = new byte[1000];
	private final byte[] ALIVE = "alive".getBytes();
	private int retries = 3;
	private DatagramSocket aSocket = null;

	private final String COMMA = ",";
	private final String POINT = ".";
	private final String VERTICAL_LINE = "|";
	private static long LocalSequenceNumber = 0;
	private int CRCAckArray[];
	private String RecipientRetransmitte[];
	private final String POS_CRC_ACK = "+veCRC";
	private final String NEG_CRC_ACK = "-veCRCC";

	private boolean flag = false;
	private String TheMulFunctionHost = null;

	//private DatagramSocket aSocket2 = 
	
	protected RequestHandler(int port, Message msg,
			ResultHandler sucessHandler, ResultHandler failureHandler,
			List<String> recipients) {
		this.port = port;
		this.msg = msg;
		this.sucessHandler = sucessHandler;
		this.failureHandler = failureHandler;
		this.recipients = recipients;
		CRCAckArray = new int[recipients.size()];
		RecipientRetransmitte = new String[recipients.size()];

	}

	protected RequestHandler() {

	}

	public static long getLocalSequenceNumner() {
		return LocalSequenceNumber;
	}

	public static synchronized void IncrementLocalSequenceNumner() {
		LocalSequenceNumber++;
	}

	@Override
	public void run() {

		try {
			pingEveryHost();
			retries = 3; /* needed later */

			byte MarshalledForm[] = MarshallPlease(msg);
			System.out.println("	>Final Marshalled Form:\n"
					+ new String(MarshalledForm));
			// here somehwere i guess i need to inc sequence #
			// RequestHandler.IncrementLocalSequenceNumner();
		
			try {
				System.out.println("	>Multicast in process..");
				aSocket = new DatagramSocket();
				for (int i = 0; i < recipients.size(); i++) {

					String aRecipientName = (String) recipients.get(i);
					InetAddress aRecipientAddress = InetAddress
							.getByName(aRecipientName);

					DatagramPacket request = new DatagramPacket(MarshalledForm,
							MarshalledForm.length, aRecipientAddress, port);
					aSocket.send(request);

					DatagramPacket reply = new DatagramPacket(buffer,
							buffer.length);
					aSocket.receive(reply);
					System.out.println("	>Reply of req: "+ new String(reply.getData()));
					// collecting the CRC acks
					CRCAckArray[i] = reply.getLength();
					RecipientRetransmitte[i] = aRecipientName;

					System.out.println("	>Its: " + CRCAckArray[i] + " & "
							+ RecipientRetransmitte[i]);
					
				}
				aSocket.close();
				/* verify collected acks and resend to only host's -ve CRC Ack */
				// flag = false;
				CheckReTransmition(MarshalledForm);
				// aSocket.close();
				
		    
		    	if(flag) throw new Exception(TheMulFunctionHost); 
		    	else{
		    		/* continue normal */
		    		if(aSocket != null) aSocket.close();
		    		//aSocket.setReuseAddress(true);
		    		System.out.println("THE PORT IS: "+port);
	    		aSocket = new DatagramSocket(port);
		    		//InetAddress address = InetAddress.getByName(RFIFOMulticast.getMyHostName());
		    		//DatagramPacket reply = new DatagramPacket(buffer, buffer.length, address, port);
		    		DatagramPacket reply = new DatagramPacket(buffer, buffer.length);
		    		
		    		System.out.println("Waiting last reply");
		    		
					aSocket.receive(reply);
					aSocket.close();
					
		    		
					System.out.println("	>Reply of round trip: "
							+ new String(reply.getData()));
					System.out.println("	>need to be unMarshalled\n");
					
					if (sucessHandler != null) {
						Message responseBack = new ReplyHandler()
								.UnMarshallPlease(reply.getData());
						System.out.println("	>The unMarshalled response:\n");
						sucessHandler.handleResult(responseBack);

					}
					
					


				}

			} catch (SocketException e) {
				System.out.println("Socket: " + e.getMessage());

				throw new Exception("some one socket!" + e.getMessage());
			} catch (IOException e) {
				System.out.println("IO: " + e.getMessage());

				throw new Exception("some one IO!" + e.getMessage());
			} finally {
				System.out.println("	>Multicast done!");
				if (aSocket != null)
					aSocket.close();
			}

		} catch (Exception e) {
			// I need to close the socket i guess
			
			System.out.println("HERERERE");
			//e.printStackTrace();
			
			System.out.println("	>" + e.getMessage());
			if (failureHandler != null) {
				Message aFailureMessage = new Message();
				aFailureMessage.put(RFIFOMulticast.RFM_MSG_DEAD_HOST,
						e.getMessage());
				failureHandler.handleResult(aFailureMessage);
			}
			// for (String key : aFailureMessage.keySet()) {
			// System.out.println("key is: " + key + " and value is: " +
			// aFailureMessage.get(key));
			// }

		}

		if (aSocket != null)
			aSocket.close();

	}

	// until here
	protected void DisplayMessage() {
		String total = "";
		for (String key : msg.keySet()) {
			System.out.println("key is: " + key + " and value is: "
					+ msg.get(key));
			total = total.concat(key.concat(msg.get(key)));
		}
	}

	private void CheckReTransmition(byte[] MarshalledForm) {
		try {

			for (int i = 0; i < CRCAckArray.length; i++) {
				System.out.println("	>DEBUG: " + CRCAckArray[i]);
				if (CRCAckArray[i] == 11) {

					System.out.println("	>Re-transmitte to.."
							+ RecipientRetransmitte[i]);
					aSocket = new DatagramSocket();

					InetAddress aRecipientAddress = InetAddress
							.getByName(RecipientRetransmitte[i]);

					DatagramPacket request = new DatagramPacket(MarshalledForm,
							MarshalledForm.length, aRecipientAddress, port);
					aSocket.send(request);

					DatagramPacket reply = new DatagramPacket(buffer,
							buffer.length);
					aSocket.receive(reply);
					System.out.println("	>Second Reply of req: "
							+ new String(reply.getData()));

					// not this may be inside
					// CRCAckArray[i] = reply.getLength();

					if (reply.getLength() == 11) {
						if (--retries != 0) {
							// re call the same method
							aSocket.close();
							CRCAckArray[i] = reply.getLength();
							System.out.println("	>REMIANING: " + retries);
							CheckReTransmition(MarshalledForm);
						} else {
							aSocket.close();
							TheMulFunctionHost = RecipientRetransmitte[i];
							flag = true;
						}
					}
					retries = 3;
				}
			}

		} catch (SocketException e) {
			System.out.println("Socket: " + e.getMessage());
			// throw new Exception("some one socket!"+e.getMessage());
		} catch (IOException e) {
			System.out.println("IO: " + e.getMessage());
			// throw new Exception("some one IO!"+e.getMessage());
		} finally {
			// System.out.println("Multicast done!");
			if (aSocket != null)
				aSocket.close();
		}

	}

	private void pingEveryHost() throws Exception {
		String aRecipientName = "";
		try {
			aSocket = new DatagramSocket();
			for (int i = 0; i < recipients.size(); i++) {

				// System.out.println("list size: "+recipients.size());

				aRecipientName = (String) recipients.get(i);
				InetAddress aRecipientAddress = InetAddress
						.getByName(aRecipientName);
				// InetAddress aRecipientAddress =
				// InetAddress.getByName("127.0.0.1");

				DatagramPacket request = new DatagramPacket(ALIVE,
						ALIVE.length, aRecipientAddress, port);
				aSocket.send(request);

				aSocket.setSoTimeout(3000); /*
												 * Wait for 3 secs if no reply
												 * throw an exception
												 */
				DatagramPacket reply = new DatagramPacket(buffer, buffer.length);
				aSocket.receive(reply);
				System.out.println("	>Host " + reply.getAddress() + " Reply: "
						+ new String(reply.getData()));
			}

		} catch (SocketTimeoutException e) {
			System.out.println("	>Time out: " + e.getMessage());
			System.out.println(retries);
			if (--retries != 0) {
				aSocket.close();
				pingEveryHost();
			} else
				throw new Exception(aRecipientName);
		} catch (SocketException e) {
			System.out.println("	>Socket: " + e.getMessage());
			throw new Exception("	>some one socket!" + e.getMessage());
		} catch (IOException e) {
			System.out.println("	>IO: " + e.getMessage());
			throw new Exception("	>some one IO!" + e.getMessage());
		}
		/*
		 * finally { if(aSocket != null) aSocket.close();
		 * System.out.println("we reached finally!!"); } else
		 * System.out.println("not yet"); }
		 */

		aSocket.close();
	}

	protected byte[] MarshallPlease(Message aMessage) {
		String total = "";
		String sub = "";
		String tmpTotal = "";
		byte MarshalledForm[] = null;

		total = total.concat(RFIFOMulticast.RFM_MSG_SENDER).concat(COMMA);
		total = total.concat(RFIFOMulticast.getMyHostName()).concat(COMMA);
		for (String key : aMessage.keySet()) {
			System.out.println("	>key is: " + key + " and value is: "
					+ aMessage.get(key));
			sub = key.concat(COMMA).concat(aMessage.get(key));
			sub = sub.concat(COMMA);
			total = total.concat(sub);
		}
		total = total.substring(0, total.length() - 1);
		total = total.concat(POINT);
		System.out.println("	>" + total);

		tmpTotal = SequenceNumber_piggyBack(total);
		// RequestHandler.IncrementLocalSequenceNumner(); //i guess the inc is
		// not here
		// System.out.println("and now: "+wholeMessage);
		MarshalledForm = appendCheckSum(tmpTotal).getBytes();
		return MarshalledForm;
	}

	private String SequenceNumber_piggyBack(String total) {
		String theSequenceNumber = "";
		theSequenceNumber = Long.toString(RequestHandler
				.getLocalSequenceNumner());
		total = (total.concat(theSequenceNumber)).concat(VERTICAL_LINE);
		return total;
	}

	private String appendCheckSum(String tmpTotal) {
		byte bytes[] = tmpTotal.getBytes();

		Checksum checkSum = new CRC32();
		checkSum.update(bytes, 0, bytes.length);
		long checkSumValue = checkSum.getValue();
		System.out.println("	>the value of check sum: " + checkSumValue);
		String st_checkSumValue = Long.toString(checkSumValue);
		tmpTotal = (tmpTotal.concat(st_checkSumValue)).concat(VERTICAL_LINE);
		System.out.println("	>After checkSum: " + tmpTotal);
		return tmpTotal;
	}
	/*
	 * public static String getMyHostName(){ String hostname = null; byte[]
	 * ipAddress = null; try { InetAddress addr = InetAddress.getLocalHost();
	 * ipAddress = addr.getAddress(); hostname = addr.getHostName();
	 * System.out.println("My hostname: "+hostname); }catch
	 * (UnknownHostException e) { e.printStackTrace(); } return hostname; }
	 */

}
